using System;
using System.Collections;
using LightCAD.MathLib;

namespace LightCAD.MathLib
{
    public class Triangle
    {
        #region scope properties or methods
        //private static Vector3 _v0 = new Vector3();
        //private static Vector3 _v1 = new Vector3();
        //private static Vector3 _v2 = new Vector3();
        //private static Vector3 _v3 = new Vector3();
        //private static Vector3 _vab = new Vector3();
        //private static Vector3 _vac = new Vector3();
        //private static Vector3 _vbc = new Vector3();
        //private static Vector3 _vap = new Vector3();
        //private static Vector3 _vbp = new Vector3();
        //private static Vector3 _vcp = new Vector3();
        private static TriangleContext GetContext() =>
            ThreadContext.GetCurrThreadContext().TriangleCtx;
        #endregion

        #region Properties



        public Vector3 A;
        public Vector3 B;
        public Vector3 C;

        #endregion

        #region constructor
        public Triangle(Vector3 a = null, Vector3 b = null, Vector3 c = null)
        {
            if (a == null) a = new Vector3();
            if (b == null) b = new Vector3();
            if (c == null) c = new Vector3();
            this.A = a;
            this.B = b;
            this.C = c;
        }
        #endregion

        #region methods
        public static Vector3 GetNormal(Vector3 a, Vector3 b, Vector3 c, Vector3 target)
        {
            var _v0 = GetContext()._v0;
            target.SubVectors(c, b);
            _v0.SubVectors(a, b);
            target.Cross(_v0);
            double targetLengthSq = target.LengthSq();
            if (targetLengthSq > 0)
            {
                return target.MulScalar(1 / Math.Sqrt(targetLengthSq));
            }
            return target.Set(0, 0, 0);
        }
  
        public static Vector3 GetBarycoord(Vector3 point, Vector3 a, Vector3 b, Vector3 c, Vector3 target)
        {
            var ctx = GetContext();
            var _v0 = ctx._v0;
            var _v1 = ctx._v1;
            var _v2 = ctx._v2;
            _v0.SubVectors(c, a);
            _v1.SubVectors(b, a);
            _v2.SubVectors(point, a);
            double dot00 = _v0.Dot(_v0);
            double dot01 = _v0.Dot(_v1);
            double dot02 = _v0.Dot(_v2);
            double dot11 = _v1.Dot(_v1);
            double dot12 = _v1.Dot(_v2);
            double denom = (dot00 * dot11 - dot01 * dot01);
            // collinear or singular triangle
            if (denom == 0)
            {
                // arbitrary location outside of triangle?
                // not sure if this is the best idea, maybe should be returning undefined
                return target.Set(-2, -1, -1);
            }
            double invDenom = 1 / denom;
            double u = (dot11 * dot02 - dot01 * dot12) * invDenom;
            double v = (dot00 * dot12 - dot01 * dot02) * invDenom;
            // barycentric coordinates must always sum to 1
            return target.Set(1 - u - v, v, u);
        }
        public static bool ContainsPoint(Vector3 point, Vector3 a, Vector3 b, Vector3 c)
        {
            var ctx = GetContext();
            var _v3 = ctx._v3;
            Triangle.GetBarycoord(point, a, b, c, _v3);
            return (_v3.X >= 0) && (_v3.Y >= 0) && ((_v3.X + _v3.Y) <= 1);
        }
        public static Vector2 GetInterpolation(Vector3 point, Vector3 p1, Vector3 p2, Vector3 p3, Vector2 uv1, Vector2 uv2, Vector2 uv3, Vector2 target)
        {
            var ctx = GetContext();
            var _v3 = ctx._v3;
            Triangle.GetBarycoord(point, p1, p2, p3, _v3);
            target.SetScalar(0);
            target.AddScaledVector(uv1, _v3.X);
            target.AddScaledVector(uv2, _v3.Y);
            target.AddScaledVector(uv3, _v3.Z);
            return target;
        }
        public static Vector3 GetInterpolation(Vector3 point, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 uv1, Vector3 uv2, Vector3 uv3, Vector3 target)
        {
            var ctx = GetContext();
            var _v3 = ctx._v3;
            Triangle.GetBarycoord(point, p1, p2, p3, _v3);
            target.SetScalar(0);
            target.AddScaledVector(uv1, _v3.X);
            target.AddScaledVector(uv2, _v3.Y);
            target.AddScaledVector(uv3, _v3.Z);
            return target;
        }
        public static bool IsFrontFacing(Vector3 a, Vector3 b, Vector3 c, Vector3 direction)
        {
            var ctx = GetContext();
            var _v0 = ctx._v0;
            var _v1 = ctx._v1;
            _v0.SubVectors(c, b);
            _v1.SubVectors(a, b);
            // strictly front facing
            return (_v0.Cross(_v1).Dot(direction) < 0) ? true : false;
        }
        public Triangle Set(Vector3 a, Vector3 b, Vector3 c)
        {
            this.A.Copy(a);
            this.B.Copy(b);
            this.C.Copy(c);
            return this;
        }
      
        public Triangle SetFromPointsAndIndices(Vector3[] points, int i0, int i1, int i2)
        {
            this.A.Copy(points[i0]);
            this.B.Copy(points[i1]);
            this.C.Copy(points[i2]);
            return this;
        }
 
        public virtual Triangle Clone()
        {
            return new Triangle().Copy(this);
        }
        public Triangle Copy(Triangle triangle)
        {
            this.A.Copy(triangle.A);
            this.B.Copy(triangle.B);
            this.C.Copy(triangle.C);
            return this;
        }
        public double GetArea()
        {
            var ctx = GetContext();
            var _v0 = ctx._v0;
            var _v1 = ctx._v1;
            _v0.SubVectors(this.C, this.B);
            _v1.SubVectors(this.A, this.B);
            return _v0.Cross(_v1).Length() * 0.5;
        }
        public Vector3 GetMidpoint(Vector3 target)
        {
            return target.AddVectors(this.A, this.B).Add(this.C).MulScalar(1 / 3);
        }
        public Vector3 GetNormal(Vector3 target)
        {
            return Triangle.GetNormal(this.A, this.B, this.C, target);
        }

        public Plane GetPlane(Plane target)
        {
            return target.SetFromCoplanarPoints(this.A, this.B, this.C);
        }
        public Vector3 GetBarycoord(Vector3 point, Vector3 target)
        {
            return Triangle.GetBarycoord(point, this.A, this.B, this.C, target);
        }
        public Vector2 GetInterpolation(Vector3 point, Vector2 uv1, Vector2 uv2, Vector2 uv3, Vector2 target)
        {
            return Triangle.GetInterpolation(point, this.A, this.B, this.C, uv1, uv2, uv3, target);
        }
        public Vector3 GetVector(string key)
        {
            switch (key)
            {
                case "a":
                case "A":
                    return this.A;
                case "b":
                case "B":
                    return this.B;
                case "c":
                case "C":
                    return this.C;
                default:
                    return null;
            }
        }
        public bool ContainsPoint(Vector3 point)
        {
            return Triangle.ContainsPoint(point, this.A, this.B, this.C);
        }
        public bool IsFrontFacing(Vector3 direction)
        {
            return Triangle.IsFrontFacing(this.A, this.B, this.C, direction);
        }
        public bool IntersectsBox(Box3 box)
        {
            return box.IntersectsTriangle(this);
        }
        public Vector3 ClosestPointToPoint(Vector3 p, Vector3 target)
        {
            Vector3 a = this.A, b = this.B, c = this.C;
            double v, w;
            // algorithm thanks to Real-Time Collision Detection by Christer Ericson,
            // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
            // under the accompanying license; see chapter 5.1.5 for detailed explanation.
            // basically, we"re distinguishing which of the voronoi regions of the triangle
            // the point lies in with the minimum amount of redundant computation.
            var ctx = GetContext();
            var _vab = ctx._vab;
            var _vac = ctx._vac;
            var _vap = ctx._vap;
            var _vbp = ctx._vbp;
            var _vcp = ctx._vcp;
            var _vbc = ctx._vbc;

            _vab.SubVectors(b, a);
            _vac.SubVectors(c, a);
            _vap.SubVectors(p, a);
            double d1 = _vab.Dot(_vap);
            double d2 = _vac.Dot(_vap);
            if (d1 <= 0 && d2 <= 0)
            {
                // vertex region of A; barycentric coords (1, 0, 0)
                return target.Copy(a);
            }
            _vbp.SubVectors(p, b);
            double d3 = _vab.Dot(_vbp);
            double d4 = _vac.Dot(_vbp);
            if (d3 >= 0 && d4 <= d3)
            {
                // vertex region of B; barycentric coords (0, 1, 0)
                return target.Copy(b);
            }
            double vc = d1 * d4 - d3 * d2;
            if (vc <= 0 && d1 >= 0 && d3 <= 0)
            {
                v = d1 / (d1 - d3);
                // edge region of AB; barycentric coords (1-v, v, 0)
                return target.Copy(a).AddScaledVector(_vab, v);
            }
            _vcp.SubVectors(p, c);
            double d5 = _vab.Dot(_vcp);
            double d6 = _vac.Dot(_vcp);
            if (d6 >= 0 && d5 <= d6)
            {
                // vertex region of C; barycentric coords (0, 0, 1)
                return target.Copy(c);
            }
            double vb = d5 * d2 - d1 * d6;
            if (vb <= 0 && d2 >= 0 && d6 <= 0)
            {
                w = d2 / (d2 - d6);
                // edge region of AC; barycentric coords (1-w, 0, w)
                return target.Copy(a).AddScaledVector(_vac, w);
            }
            double va = d3 * d6 - d5 * d4;
            if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0)
            {
                _vbc.SubVectors(c, b);
                w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
                // edge region of BC; barycentric coords (0, 1-w, w)
                return target.Copy(b).AddScaledVector(_vbc, w); // edge region of BC
            }
            // face region
            double denom = 1 / (va + vb + vc);
            // u = va * denom
            v = vb * denom;
            w = vc * denom;
            return target.Copy(a).AddScaledVector(_vab, v).AddScaledVector(_vac, w);
        }
        public virtual bool Equals(Triangle triangle)
        {
            return triangle.A.Equals(this.A) && triangle.B.Equals(this.B) && triangle.C.Equals(this.C);
        }
        #endregion

    }
    public sealed class TriangleContext
    {
        public readonly Vector3 _v0 = new Vector3();
        public readonly Vector3 _v1 = new Vector3();
        public readonly Vector3 _v2 = new Vector3();
        public readonly Vector3 _v3 = new Vector3();
        public readonly Vector3 _vab = new Vector3();
        public readonly Vector3 _vac = new Vector3();
        public readonly Vector3 _vbc = new Vector3();
        public readonly Vector3 _vap = new Vector3();
        public readonly Vector3 _vbp = new Vector3();
        public readonly Vector3 _vcp = new Vector3();
    }
}
